﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Text;
using System.Linq.Expressions;
using System.Web.Routing;
using System.Reflection;
using Jacquette.DynamicUrls.Support;


namespace Jacquette.DynamicUrls {

    public class JsGetJsonOptions {
        public JsGetJsonOptions() {
            LoadingMessage = "Loading...";
            JsSuccessFn = "function(data){}";
        }

        public string LoadingMessage { get; set; }
        public string JsSuccessFn { get; set; }
    }

    public class JsLoadOptions {
        public static JsLoadOptions Defaults = new JsLoadOptions {
            HtmlNewElement = "",
            JsNewElement = "",
            HtmlElementsBeforeResults = "",
            JsElementsBeforeResults = "",
            HtmlElementsAfterResults = "",
            JsElementsAfterResults = "",
            JsNamePrefix = "null",
            JsData = "null",
            /* this = newly created element */
            JsSuccessCallbackFunc = "function(responseText, textStatus, XMLHttpRequest){}",
            JsErrorCallbackFunc = "function(XMLHttpRequest, error){}",
            CssLoadingClass = "ajaxLoading",
            InsertionMode = JsLoadInsertionMode.Append,
        };

        public JsLoadOptions() {
            if (Defaults != null) {
                HtmlNewElement = Defaults.HtmlNewElement;
                JsNewElement = Defaults.JsNewElement;
                HtmlElementsBeforeResults = Defaults.HtmlElementsBeforeResults;
                JsElementsBeforeResults = Defaults.JsElementsBeforeResults;
                HtmlElementsAfterResults = Defaults.HtmlElementsAfterResults;
                JsElementsAfterResults = Defaults.JsElementsAfterResults;
                JsNamePrefix = Defaults.JsNamePrefix;
                JsData = Defaults.JsData;
                /* this = newly created element */
                JsSuccessCallbackFunc = Defaults.JsSuccessCallbackFunc;
                JsErrorCallbackFunc = Defaults.JsErrorCallbackFunc;
                CssLoadingClass = Defaults.CssLoadingClass;
                InsertionMode = Defaults.InsertionMode;
            }
        }
        public string HtmlNewElement { get; set; }
        public string JsNewElement { get; set; }
        public string HtmlElementsBeforeResults { get; set; }
        public string JsElementsBeforeResults { get; set; }
        public string HtmlElementsAfterResults { get; set; }
        public string JsElementsAfterResults { get; set; }
        public string JsTargetSelector { get; set; }
        public string JsData { get; set; }
        public string JsSuccessCallbackFunc { get; set; }
        public string JsErrorCallbackFunc { get; set; }
        public string CssLoadingClass { get; set; }
        public string JsNamePrefix { get; set; }
        public JsLoadInsertionMode InsertionMode { get; set; }

    }

    public enum JsLoadInsertionMode : int {
        Prepend,
        Append,
        Replace,
    }

    public class JsDialogButton {
        public string Name { get; set; }
        public string Function { get; set; }
    }
    

    public interface IDynamicUrlsMixin{
        ViewDataDictionary ViewData{get;}
        ViewContext ViewContext { get; }
        HtmlHelper<object> Html { get; }
    }

    public interface IDynamicUrlsMixin<TModel> : IDynamicUrlsMixin where TModel : class, new() {
        new HtmlHelper<TModel> Html { get; }
    }

    public static class JsHelperExtensions {
        public static string HtmlNamePrefix(this IDynamicUrlsMixin me){
            return me.ViewData.ContainsKey("HtmlNamePrefix") ? (String)me.ViewData["HtmlNamePrefix"] : "";
        }


        public static string JsNameFor<TModel>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, Object>> expression) where TModel : class, new() {
            return me.JsNameFor(expression.Body);
        }

        public static string JsNameFor<TModel, T1>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, Object>> expression) where TModel : class, new() {
            return me.JsNameFor(expression.Body);
        }

        public static string JsNameFor<TModel, T1, T2>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, T2, Object>> expression) where TModel : class, new() {
            return me.JsNameFor(expression.Body);
        }

        public static string JsNameFor<TModel>(this IDynamicUrlsMixin<TModel> me, Expression expression)
            where TModel : class, new() {
            var name = new ExpressionJsNameVisitor().Visit(expression);
            return me.HtmlNamePrefix() + 
                    (string.IsNullOrEmpty(me.HtmlNamePrefix()) || string.IsNullOrEmpty(name) || name.StartsWith("[")
                            ? null
                            : ".") +
                    name;
        }

        public static string JqSelectorFor<TModel>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, Object>> expression) where TModel : class, new() {
            return me.JqSelectorFor(expression.Body);
        }

        public static string JqSelectorFor<TModel, T1>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, Object>> expression) where TModel : class, new() {
            return me.JqSelectorFor(expression.Body);
        }

        public static string JqSelectorFor<TModel, T1, T2>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, T2, Object>> expression) where TModel : class, new() {
            return me.JqSelectorFor(expression.Body);
        }

        public static string JqSelectorFor<TModel>(this IDynamicUrlsMixin<TModel> me, Expression expression)
            where TModel : class, new() {
            return "$('#' + " + me.JsIdFor(expression) + ")";
        }

        public static string JsIdFor<TModel>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, Object>> expression) where TModel : class, new() {
            return me.JsIdFor(expression.Body);
        }

        public static string JsIdFor<TModel, T1>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, Object>> expression) where TModel : class, new() {
            return me.JsIdFor(expression.Body);
        }

        public static string JsIdFor<TModel, T1, T2>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, T2, Object>> expression) where TModel : class, new() {
            return me.JsIdFor(expression.Body);
        }

        public static string JsIdFor<TModel>(this IDynamicUrlsMixin<TModel> me, Expression expression)
            where TModel : class, new() {
            return me.JsNameFor(expression).Replace("[", "_").Replace("]", "_").Replace(".", "_");
        }


        public static string Jq<TModel>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, Object>> expression) where TModel : class, new() {
            return me.Jq(expression.Body);
        }

        public static string Jq<TModel, T1>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, Object>> expression) where TModel : class, new() {
            return me.Jq(expression.Body);
        }

        public static string Jq<TModel, T1, T2>(this IDynamicUrlsMixin<TModel> me, Expression<Func<TModel, T1, T2, Object>> expression) where TModel : class, new() {
            return me.Jq(expression.Body);
        }

        private static string Jq<TModel>(this IDynamicUrlsMixin<TModel> me, Expression expression)
            where TModel : class, new() {
            return "$('#' + " + me.JsIdFor(expression) + ")";
        }
        /**
         * Converts like BuildUrlFromExpression, but the parameters to the second lambda expression are converted 
         * into their javascript equivalents.  For example, 
         * 
         * JsUrl<UserController, int>((c, id) => c.Details(123, id))
         * 
         * Produces:
         * '/User/Details?firstParam=123&secondParam' + id + ''
         * 
         * The advantage is that this is largely type safe, and provides a good way get to pass parameters from javascript back to the server
         **/
        public static string JsUrl<TController, T1>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, ActionResult>> action)
           where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController>(this IDynamicUrlsMixin me, Expression<Func<TController, ActionResult>> action)
           where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController, T1, T2>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, ActionResult>> action) where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController, T1, T2, T3>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, ActionResult>> action) where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController, T1, T2, T3, T4>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, ActionResult>> action) where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController, T1, T2, T3, T4, T5>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, T5, ActionResult>> action) where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController, T1, T2, T3, T4, T5, T6>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, T5, T6, ActionResult>> action) where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController, T1, T2, T3, T4, T5, T6, T7>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, T5, T6, T7, ActionResult>> action) where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController, T1, T2, T3, T4, T5, T6, T7, T8>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, T5, T6, T7, T8, ActionResult>> action) where TController : Controller {
            return me.JsUrl<TController>(action.Body as MethodCallExpression);
        }

        public static string JsUrl<TController>(this IDynamicUrlsMixin me, MethodCallExpression call)
           where TController : Controller {
            return JsUrl<TController>(call, me.Html.RouteCollection, me.ViewContext.RequestContext);
        }

        public static string UrlFor<TController>(this IDynamicUrlsMixin me, Expression<Func<TController, ActionResult>> action)
           where TController : Controller {
            return me.UrlFor<TController>(action.Body as MethodCallExpression);
        }

        public static string UrlFor<TController>(this IDynamicUrlsMixin me, MethodCallExpression action)
           where TController : Controller {
            var jsUrl = JsUrl<TController>(action, me.Html.RouteCollection, me.ViewContext.RequestContext);
            return jsUrl.Substring(1, jsUrl.Length - 2);
        }

        public static string JsUrl<TController>(MethodCallExpression call, RouteCollection routeCollection, RequestContext context)
            where TController : Controller {

            string controllerName = typeof(TController).Name;
            if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)) {
                throw new ArgumentException("action");
            }
            controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);
            if (controllerName.Length == 0) {
                throw new ArgumentException("action");
            }

            // TODO: How do we know that this method is even web callable?
            //      For now, we just let the call itself throw an exception.

            var rvd = new RouteValueDictionary();
            rvd.Add("Controller", controllerName);
            rvd.Add("Action", call.Method.Name);
            ParameterInfo[] parameters = call.Method.GetParameters();

            var paramTagToParamName = new Dictionary<string, string>();
            if (parameters.Length > 0) {
                for (int i = 0; i < parameters.Length; i++) {
                    Expression arg = call.Arguments[i];
                    ConstantExpression ce = arg as ConstantExpression;
                    if (ce != null) {
                        // If argument is a constant expression, just get the value
                        var value = ce.Value;
                        rvd.Add(parameters[i].Name, value);

                        //passed in params are marked with tags to be replace by javascript variables later.
                        //UnaryExpression is used to ignore autoboxing.
                    } else if (arg is ParameterExpression || arg is UnaryExpression && (arg as UnaryExpression).NodeType == ExpressionType.Convert && (arg as UnaryExpression).Operand is ParameterExpression) {
                        ParameterExpression parameterExpression;
                        if (arg is UnaryExpression)
                            parameterExpression = (arg as UnaryExpression).Operand as ParameterExpression;
                        else
                            parameterExpression = arg as ParameterExpression;
                        var value = "%%_%_%_%_%_%_%_%" + i + "%_%_%_%_%_%_%_%%";
                        paramTagToParamName[(string)value] = parameterExpression.Name;
                        rvd.Add(parameters[i].Name, value);
                    } else {
                        // Otherwise, convert the argument subexpression to type object,
                        // make a lambda out of it, compile it, and invoke it to get the value
                        Expression<Func<object>> lambdaExpression = Expression.Lambda<Func<object>>(Expression.Convert(arg, typeof(object)));
                        Func<object> func = lambdaExpression.Compile();
                        var value = func();
                        if (value != null) {
                            foreach (var pair in value.AsDictionary(parameters[i].Name, 5)) {
                                rvd.Add(pair.Key, pair.Value);
                            }
                        }
                    }
                }
            }
            var rawPath = routeCollection.GetVirtualPath(context, rvd).VirtualPath.JsEncode();
            foreach (var pair in paramTagToParamName)
                rawPath = rawPath.Replace(pair.Key.UrlEncode(), "' + escape(typeof(" + pair.Value.ToString() + ") == 'function' ? " + pair.Value.ToString() + "() : " + pair.Value.ToString() + " ) + '");
            return rawPath;
        }


        public static string JsGetJson<TController>(this IDynamicUrlsMixin me, Expression<Func<TController, JsonResult>> action, JsGetJsonOptions options)
           where TController : Controller {
            return me.JsGetJson<TController>(action.Body as MethodCallExpression, options);
        }

        public static string JsGetJson<TController, T1>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, JsonResult>> action, JsGetJsonOptions options)
           where TController : Controller {
            return me.JsGetJson<TController>(action.Body as MethodCallExpression, options);
        }

        public static string JsGetJson<TController, T1, T2>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, JsonResult>> action, JsGetJsonOptions options)
            where TController : Controller {
            return me.JsGetJson<TController>(action.Body as MethodCallExpression, options);
        }

        public static string JsGetJson<TController, T1, T2, T3>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, JsonResult>> action, JsGetJsonOptions options)
            where TController : Controller {
            return me.JsGetJson<TController>(action.Body as MethodCallExpression, options);
        }

        private static string JsGetJson<TController>(this IDynamicUrlsMixin me, MethodCallExpression call, JsGetJsonOptions options)
           where TController : Controller {
            var useLoadingDialog = !options.LoadingMessage.IsNullOrEmpty();
            return
                "if(" + useLoadingDialog + ")" +
                    "openLoadingDialog(" + options.LoadingMessage.JsEncode() + ");" +
                "$.ajax({" +
                    "type: 'POST', " +
                    "url: " + me.JsUrl<TController>(call) + "," +
                    "error: function(x, e){closeLoadingDialog();showXhrErrorDialog(x, e);}, " +
                    "success: function(data){ " +
                        "if(" + useLoadingDialog + ")" +
                            "closeLoadingDialog();" +
                        options.JsSuccessFn + "(eval('(' + data + ')'));" +
                    "}" +
                "})";
        }

        public static string JsRedirect<TController>(this IDynamicUrlsMixin me, Expression<Func<TController, ActionResult>> action)
            where TController : Controller {
            return me.JsRedirect<TController>(action.Body as MethodCallExpression);
        }

        public static string JsRedirect<TController, T1>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, ActionResult>> action)
            where TController : Controller {
            return me.JsRedirect<TController>(action.Body as MethodCallExpression);
        }

        public static string JsRedirect<TController, T1, T2>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, ActionResult>> action)
            where TController : Controller {
            return me.JsRedirect<TController>(action.Body as MethodCallExpression);
        }

        public static string JsRedirect<TController, T1, T2, T3>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, ActionResult>> action)
            where TController : Controller {
            return me.JsRedirect<TController>(action.Body as MethodCallExpression);
        }

        public static string JsRedirect<TController, T1, T2, T3, T4>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, ActionResult>> action)
            where TController : Controller {
            return me.JsRedirect<TController>(action.Body as MethodCallExpression);
        }

        public static string JsRedirect<TController, T1, T2, T3, T4, T5>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, T5, ActionResult>> action)
            where TController : Controller {
            return me.JsRedirect<TController>(action.Body as MethodCallExpression);
        }

        public static string JsRedirect<TController, T1, T2, T3, T4, T5, T6>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, T4, T5, T6, ActionResult>> action)
            where TController : Controller {
            return me.JsRedirect<TController>(action.Body as MethodCallExpression);
        }

        private static string JsRedirect<TController>(this IDynamicUrlsMixin me, MethodCallExpression call)
           where TController : Controller {
            return "window.location=" + me.JsUrl<TController>(call);
        }

        public static string JsLoadElement<TController>(this IDynamicUrlsMixin me, Expression<Func<TController, ActionResult>> action, JsLoadOptions option)
            where TController : Controller {
            return me.JsLoadElement<TController>(action.Body as MethodCallExpression, option);
        }

        public static string JsLoadElement<TController, T1>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, ActionResult>> action, JsLoadOptions option)
            where TController : Controller {
            return me.JsLoadElement<TController>(action.Body as MethodCallExpression, option);
        }

        public static string JsLoadElement<TController, T1, T2>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, ActionResult>> action, JsLoadOptions option)
            where TController : Controller {
            return me.JsLoadElement<TController>(action.Body as MethodCallExpression, option);
        }

        public static string JsLoadElement<TController, T1, T2, T3>(this IDynamicUrlsMixin me, Expression<Func<TController, T1, T2, T3, ActionResult>> action, JsLoadOptions option)
            where TController : Controller {
            return me.JsLoadElement<TController>(action.Body as MethodCallExpression, option);
        }

        private static string JsLoadElement<TController>(this IDynamicUrlsMixin me, MethodCallExpression call, JsLoadOptions options)
           where TController : Controller {
            return me.JsLoadElement(me.JsUrl<TController>(call), options, "null");
        }

        private static string JsLoadElement<TController>(this IDynamicUrlsMixin me, MethodCallExpression call, JsLoadOptions options, string jsForm)
           where TController : Controller {
            return me.JsLoadElement(me.JsUrl<TController>(call), options, jsForm);
        }

        public static string JsLoadElement(this IDynamicUrlsMixin me, string jsUrl, JsLoadOptions options, string jsForm) {
            return options.JsTargetSelector.Or("$()") + ".loadWithLoading(" +
                options.JsNewElement.Or(options.HtmlNewElement.JsEncode()) + ", " +
                jsUrl + ", " +
                options.JsNamePrefix + ", " +
                options.JsData + ", " +
                options.JsSuccessCallbackFunc + ", " +
                options.JsErrorCallbackFunc + ", " +
                options.JsElementsBeforeResults.Or(options.HtmlElementsBeforeResults.JsEncode()) + ", " +
                options.JsElementsAfterResults.Or(options.HtmlElementsAfterResults.JsEncode()) + ", " +
                options.InsertionMode.ToString().JsEncode() + ", " +
                options.CssLoadingClass.JsEncode() + ", " +
                jsForm +
            ")";
        }
    }
}


namespace Jacquette.DynamicUrls.Support {
    /**
        * this class is taken verbatim from trunk/src/MvcContrib.FluentHtml/Expressions
        * grab a new version from there if needed at some point.  The only change is Visit() 
        * is made virtual, so that it can be overriden in ExpressionJsNameVisitor
        **/
    public class ExpressionNameVisitor {
        public string Visit(Expression expression) {
            if (expression is LambdaExpression) {
                expression = ((LambdaExpression)expression).Body;
            }
            if (expression is UnaryExpression) {
                expression = ((UnaryExpression)expression).Operand;
            }
            if (expression is MethodCallExpression) {
                return Visit((MethodCallExpression)expression);
            }
            if (expression is MemberExpression) {
                return Visit((MemberExpression)expression);
            }
            if (expression is BinaryExpression && expression.NodeType == ExpressionType.ArrayIndex) {
                return Visit((BinaryExpression)expression);
            }
            return null;
        }

        private string Visit(BinaryExpression expression) {
            string result = null;
            if (expression.Left is MemberExpression) {
                result = Visit((MemberExpression)expression.Left);
            }
            var index = Expression.Lambda(expression.Right).Compile().DynamicInvoke();
            return result + string.Format("[{0}]", index);
        }

        private string Visit(MemberExpression expression) {
            var name = expression.Member.Name;
            var ancestorName = Visit(expression.Expression);
            if (ancestorName != null) {
                name = ancestorName + "." + name;
            }
            return name;
        }

        protected virtual string Visit(MethodCallExpression expression) {
            string name = null;
            if (expression.Object is MemberExpression) {
                name = Visit((MemberExpression)expression.Object);
            }

            //TODO: Is there a more certain way to determine if this is an indexed property?
            if (expression.Method.Name == "get_Item" && expression.Arguments.Count == 1) {
                var index = Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke();
                name += string.Format("[{0}]", index);
            }
            return name;
        }
    }

    public class ExpressionJsNameVisitor : ExpressionNameVisitor {

        protected override string Visit(MethodCallExpression expression) {
            string name = null;
            if (expression.Object is MemberExpression) {
                name = Visit((MemberExpression)expression.Object);
            }

            //TODO: Is there a more certain way to determine if this is an indexed property?
            if (expression.Method.Name == "get_Item" && expression.Arguments.Count == 1) {
                if (expression.Arguments[0] is ParameterExpression)
                    name += string.Format("[' + {0} + ']", expression.Arguments[0].ToString());
                else {
                    var index = Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke();
                    name += string.Format("[{0}]", index);
                }
            }
            return name;
        }
    }

    public static class ExpressionExtensions {
        public static string MemberName(this LambdaExpression me) {
            return new ExpressionNameVisitor().Visit(me);
        }

        public static Type MemberType(this LambdaExpression me) {
            if (me.Body.NodeType == ExpressionType.Convert)
                return ((UnaryExpression)me.Body).Operand.Type;
            else
                return me.Body.Type;
        }
    }

    public static class JsonExtensions {
        public static Dictionary<string, string> AsDictionary(this object obj, string namePrefix, int maxRecursionDepth) {
            var result = new Dictionary<string, string>();
            if (maxRecursionDepth == 0 || obj == null)
                return result;
            var type = obj.GetType();
            if (type.IsArray || type.Name.StartsWith("List`")) {
                var list = obj as System.Collections.IList;
                var index = 0;
                foreach (var o in list) {
                    result = result.Union(o.AsDictionary(namePrefix + "[" + index + "]", maxRecursionDepth - 1));
                    ++index;
                }
            } else if (type.IsPrimitive || type.IsEnum || type == typeof(string)) {
                result[namePrefix] = obj.ToString();
            } else {
                foreach (var property in type.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.GetProperty)) {
                    var value = property.GetValue(obj, new object[] { });
                    result = result.Union(value.AsDictionary(namePrefix + "." + property.Name, maxRecursionDepth - 1));
                }
            }
            return result;
        }

        public static Dictionary<A, B> Union<A, B>(this Dictionary<A, B> me, Dictionary<A, B> other) {
            var result = new Dictionary<A, B>();
            foreach (var pair in me)
                result[pair.Key] = pair.Value;
            foreach (var pair in other)
                result[pair.Key] = pair.Value;
            return result;
        }


    }

    public static class StringExtensions {
        public static bool IsNullOrEmpty(this string s) {
            return string.IsNullOrEmpty(s);
        }


        public static String UrlEncode(this String s) {
            return HttpUtility.UrlEncode(s);
        }


        public static string Or(this string s, string other) {
            return s.IsNullOrEmpty() ? other : s;
        }

        public static String JsEncode(this string s) {
            if (s == null)
                return "''";
            StringBuilder sb = new StringBuilder();
            sb.Append("'");
            foreach (char c in s) {
                switch (c) {
                    case '\'':
                        sb.Append("\\'");
                        break;
                    case '\"':
                        sb.Append("\\\"");
                        break;
                    case '\\':
                        sb.Append("\\\\");
                        break;
                    case '\b':
                        sb.Append("\\b");
                        break;
                    case '\f':
                        sb.Append("\\f");
                        break;
                    case '\n':
                        sb.Append("\\n");
                        break;
                    case '\r':
                        sb.Append("\\r");
                        break;
                    case '\t':
                        sb.Append("\\t");
                        break;
                    default:
                        int i = (int)c;
                        if (i < 32 || i > 127) {
                            sb.AppendFormat("\\u{0:X04}", i);
                        } else {
                            sb.Append(c);
                        }
                        break;
                }
            }
            sb.Append("'");


            return sb.ToString().Replace("</script>", "</scri' + 'pt>");

        }
    }
}

